# LeetCode 72、编辑距离
# 一、题目描述
给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。
你可以对一个单词进行如下三种操作:
- 1、插入一个字符
- 2、删除一个字符
- 3、替换一个字符
# 二、题目解析
# 三、参考代码
# 1、Java 代码
class Solution {
public int minDistance(String word1, String word2) {
// 获取字符串 word1 的长度
int L1 = word1.length();
// 获取字符串 word2 的长度
int L2 = word2.length();
// 设置二维数组 dp 用来储存 word1 的前 i 个字符转换为 word2 的前 j 个字符最少操作数
// dp[0][0] 表示 word1 的前 0 个字符转换为 word2 的前 0 个字符最少操作数
// dp[0][j] 表示 word1 的前 0 个字符转换为 word2 的前 j 个字符最少操作数
// dp[i][0] 表示 word1 的前 i 个字符转换为 word2 的前 0 个字符最少操作数
// dp[i][j] 表示 word1 的前 i 个字符转换为 word2 的前 j 个字符最少操作数
// dp[L1][L2] 表示 word1 的前 L1 个字符转换为 word2 的前 L2 个字符最少操作数
int[][] dp = new int[L1 + 1][L2 + 1];
// dp[i][0] 表示 word1 的前 i 个字符转换为 word2 的前 0 个字符最少操作数
// dp[i][0] 相当于给二维数组 dp 的第一列进行赋值操作
// 只需要每次执行删除操作,就可以把 word1 的字符删除到 0 个字符
for(int i = 0 ; i <= L1 ; i++){
// dp[1][0] 表示需要删除 1 次才能删除到 0 个字符
// dp[2][0] 表示需要删除 2 次才能删除到 2 个字符
// dp[i][0] 表示需要删除 i 次才能删除到 2 个字符
dp[i][0] = i;
}
// dp[0][j] 表示 word1 的前 0 个字符转换为 word2 的前 j 个字符最少操作数
// dp[0][j] 相当于给二维数组 dp 的第一行进行赋值操作
// 只需要每次执行插入操作,就可以把 word1 的字符转行为 j 个字符
for(int j = 0 ;j <= L2 ; j++){
// dp[0][1] 表示需要插入 1 次才能得到 1 个字符
// dp[0][2] 表示需要插入 2 次才能得到 2 个字符
// dp[0][j] 表示需要插入 j 次才能得到 j 个字符
dp[0][j] = j;
}
// 通过两个 for 循环,设置二维数组 dp 中的所有元素的值
// i 从 word1 的第 1 个字符一直到 L1 个字符
for(int i = 1 ; i <= L1 ; i++){
// j 从 word2 的第 1 个字符一直到 L2 个字符
for(int j = 1 ; j <= L2 ; j++){
// 二维数组 dp 的下标是从 (0,0) 开始的
// 所以 dp[i][j] 表示的是 word1 中前 i 个字符即位置为 i - 1 的字符
// 转换为 word2 中前 j 个字符即位置为 j - 1 的字符所需要的最少操作数
// 如果此时 word1 中第 i - 1 个位置的字符(即当前遍历 i 时的字符)
// 与 word2 中第 j - 1 个位置的字符【相等】(即当前遍历 j 时的字符)
// 说明 word1 的前 i - 1 个字符转换为 word2 中前 j - 1 个字符成功后,
// word1 的前 i 个字符也就成功转换为 word2 的前 j 个字符了
if(word1.charAt(i - 1) == word2.charAt(j - 1)){
// dp[i][j] 表示 word1 的前 i 个字符转换为 word2 的前 j 个字符最少操作数
// dp[i - 1][j - 1] 表示 word1 的前 i - 1 个字符转换为 word2 的前 j -1 个字符最少操作数
// 比如 word1 为 abcd,word2 为 efgd
// 此时 i = 4,j = 4
// 由于 word1.charAt(4 - 1) = d
// word2.charAt(4 - 1) = d
// 所以如果知道了 abc 转换为 efg 的最少操作数,那么结果也就出来了
// d --> d 不需要执行任何操作
dp[i][j] = dp[i - 1][j - 1];
// 否则,说明 word1 中第 i - 1 个位置的字符(即当前遍历 i 时的字符)
// 与 word2 中第 j - 1 个位置的字符【不相等】(即当前遍历 j 时的字符)
}else{
// 那么 dp[i][j] 可以从以下三种状态转换过来
// 1、word1(L1-1) ——> word2(L2-1)
// 2、word1(L1-1) ——> word2(L2)
// 3、word1(L1) ——> word2(L2-1)
// word1(L1-1) ——> word2(L2-1) 的最少操作数是 dp[i - 1][j - 1]
// word1(L1-1) ——> word2(L2) 的最少操作数是 dp[i - 1][j]
// word1(L1) ——> word2(L2-1) 的最少操作数是 dp[i][j - 1])
// 选这三种状态的较小值后,
// 1、如果是 word1(L1-1) ——> word2(L2-1) 过来的,再执行 1 次替换操作
// 2、如果是 word1(L1-1) ——> word2(L2) 过来的,再执行 1 次删除操作
// 3、如果是 word1(L1) ——> word2(L2-1) 过来的,再执行 1 次插入操作
dp[i][j] = Math.min(dp[i - 1][j - 1],Math.min(dp[i - 1][j],dp[i][j - 1])) + 1;
}
}
}
// dp[L1][L2] 表示 word1 的前 L1 个字符转换为 word2 的前 L2 个字符最少操作数
// 把这个结果进行返回
return dp[L1][L2];
}
}
# **2、**C++ 代码
class Solution {
public:
int minDistance(string word1, string word2) {
// 获取字符串 word1 的长度
int L1 = word1.length();
// 获取字符串 word2 的长度
int L2 = word2.length();
// 设置二维数组 dp 用来储存 word1 的前 i 个字符转换为 word2 的前 j 个字符最少操作数
// dp[0][0] 表示 word1 的前 0 个字符转换为 word2 的前 0 个字符最少操作数
// dp[0][j] 表示 word1 的前 0 个字符转换为 word2 的前 j 个字符最少操作数
// dp[i][0] 表示 word1 的前 i 个字符转换为 word2 的前 0 个字符最少操作数
// dp[i][j] 表示 word1 的前 i 个字符转换为 word2 的前 j 个字符最少操作数
// dp[L1][L2] 表示 word1 的前 L1 个字符转换为 word2 的前 L2 个字符最少操作数
auto dp = vector< vector <int> > ( L1 + 1,vector <int> ( L2 + 1 ));
// dp[i][0] 表示 word1 的前 i 个字符转换为 word2 的前 0 个字符最少操作数
// dp[i][0] 相当于给二维数组 dp 的第一列进行赋值操作
// 只需要每次执行删除操作,就可以把 word1 的字符删除到 0 个字符
for(int i = 0 ; i <= L1 ; i++){
// dp[1][0] 表示需要删除 1 次才能删除到 0 个字符
// dp[2][0] 表示需要删除 2 次才能删除到 2 个字符
// dp[i][0] 表示需要删除 i 次才能删除到 2 个字符
dp[i][0] = i;
}
// dp[0][j] 表示 word1 的前 0 个字符转换为 word2 的前 j 个字符最少操作数
// dp[0][j] 相当于给二维数组 dp 的第一行进行赋值操作
// 只需要每次执行插入操作,就可以把 word1 的字符转行为 j 个字符
for(int j = 0 ;j <= L2 ; j++){
// dp[0][1] 表示需要插入 1 次才能得到 1 个字符
// dp[0][2] 表示需要插入 2 次才能得到 2 个字符
// dp[0][j] 表示需要插入 j 次才能得到 j 个字符
dp[0][j] = j;
}
// 通过两个 for 循环,设置二维数组 dp 中的所有元素的值
// i 从 word1 的第 1 个字符一直到 L1 个字符
for(int i = 1 ; i <= L1 ; i++){
// j 从 word2 的第 1 个字符一直到 L2 个字符
for(int j = 1 ; j <= L2 ; j++){
// 二维数组 dp 的下标是从 (0,0) 开始的
// 所以 dp[i][j] 表示的是 word1 中前 i 个字符即位置为 i - 1 的字符
// 转换为 word2 中前 j 个字符即位置为 j - 1 的字符所需要的最少操作数
// 如果此时 word1 中第 i - 1 个位置的字符(即当前遍历 i 时的字符)
// 与 word2 中第 j - 1 个位置的字符【相等】(即当前遍历 j 时的字符)
// 说明 word1 的前 i - 1 个字符转换为 word2 中前 j - 1 个字符成功后,
// word1 的前 i 个字符也就成功转换为 word2 的前 j 个字符了
if(word1[i - 1] == word2[j - 1]){
// dp[i][j] 表示 word1 的前 i 个字符转换为 word2 的前 j 个字符最少操作数
// dp[i - 1][j - 1] 表示 word1 的前 i - 1 个字符转换为 word2 的前 j -1 个字符最少操作数
// 比如 word1 为 abcd,word2 为 efgd
// 此时 i = 4,j = 4
// 由于 word1.charAt(4 - 1) = d
// word2.charAt(4 - 1) = d
// 所以如果知道了 abc 转换为 efg 的最少操作数,那么结果也就出来了
// d --> d 不需要执行任何操作
dp[i][j] = dp[i - 1][j - 1];
// 否则,说明 word1 中第 i - 1 个位置的字符(即当前遍历 i 时的字符)
// 与 word2 中第 j - 1 个位置的字符【不相等】(即当前遍历 j 时的字符)
}else{
// 那么 dp[i][j] 可以从以下三种状态转换过来
// 1、word1(L1-1) ——> word2(L2-1)
// 2、word1(L1-1) ——> word2(L2)
// 3、word1(L1) ——> word2(L2-1)
// word1(L1-1) ——> word2(L2-1) 的最少操作数是 dp[i - 1][j - 1]
// word1(L1-1) ——> word2(L2) 的最少操作数是 dp[i - 1][j]
// word1(L1) ——> word2(L2-1) 的最少操作数是 dp[i][j - 1])
// 选这三种状态的较小值后,
// 1、如果是 word1(L1-1) ——> word2(L2-1) 过来的,再执行 1 次替换操作
// 2、如果是 word1(L1-1) ——> word2(L2) 过来的,再执行 1 次删除操作
// 3、如果是 word1(L1) ——> word2(L2-1) 过来的,再执行 1 次插入操作
dp[i][j] = min(dp[i - 1][j - 1],min(dp[i - 1][j],dp[i][j - 1])) + 1;
}
}
}
// dp[L1][L2] 表示 word1 的前 L1 个字符转换为 word2 的前 L2 个字符最少操作数
// 把这个结果进行返回
return dp[L1][L2];
}
};
# 3、Python 代码
class Solution:
def minDistance(self, word1: str, word2: str) -> int:
# 获取字符串 word1 的长度
L1 = len(word1)
# 获取字符串 word2 的长度
L2 = len(word2)
# 设置二维数组 dp 用来储存 word1 的前 i 个字符转换为 word2 的前 j 个字符最少操作数
# dp[0][0] 表示 word1 的前 0 个字符转换为 word2 的前 0 个字符最少操作数
# dp[0][j] 表示 word1 的前 0 个字符转换为 word2 的前 j 个字符最少操作数
# dp[i][0] 表示 word1 的前 i 个字符转换为 word2 的前 0 个字符最少操作数
# dp[i][j] 表示 word1 的前 i 个字符转换为 word2 的前 j 个字符最少操作数
# dp[L1][L2] 表示 word1 的前 L1 个字符转换为 word2 的前 L2 个字符最少操作数
dp = [[0] * (L2 + 1) for _ in range( L1 + 1)]
# dp[i][0] 表示 word1 的前 i 个字符转换为 word2 的前 0 个字符最少操作数
# dp[i][0] 相当于给二维数组 dp 的第一列进行赋值操作
# 只需要每次执行删除操作,就可以把 word1 的字符删除到 0 个字符
for i in range(L1 + 1):
# dp[1][0] 表示需要删除 1 次才能删除到 0 个字符
# dp[2][0] 表示需要删除 2 次才能删除到 2 个字符
# dp[i][0] 表示需要删除 i 次才能删除到 2 个字符
dp[i][0] = i
# dp[0][j] 表示 word1 的前 0 个字符转换为 word2 的前 j 个字符最少操作数
# dp[0][j] 相当于给二维数组 dp 的第一行进行赋值操作
# 只需要每次执行插入操作,就可以把 word1 的字符转行为 j 个字符
for j in range( L2 + 1):
# dp[0][1] 表示需要插入 1 次才能得到 1 个字符
# dp[0][2] 表示需要插入 2 次才能得到 2 个字符
# dp[0][j] 表示需要插入 j 次才能得到 j 个字符
dp[0][j] = j
# 通过两个 for 循环,设置二维数组 dp 中的所有元素的值
# i 从 word1 的第 1 个字符一直到 L1 个字符
for i in range( 1 , L1 + 1):
# j 从 word2 的第 1 个字符一直到 L2 个字符
for j in range( 1 , L2 + 1):
# 二维数组 dp 的下标是从 (0,0) 开始的
# 所以 dp[i][j] 表示的是 word1 中前 i 个字符即位置为 i - 1 的字符
# 转换为 word2 中前 j 个字符即位置为 j - 1 的字符所需要的最少操作数
# 如果此时 word1 中第 i - 1 个位置的字符(即当前遍历 i 时的字符)
# 与 word2 中第 j - 1 个位置的字符【相等】(即当前遍历 j 时的字符)
# 说明 word1 的前 i - 1 个字符转换为 word2 中前 j - 1 个字符成功后,
# word1 的前 i 个字符也就成功转换为 word2 的前 j 个字符了
if word1[i - 1] == word2[j - 1] :
# dp[i][j] 表示 word1 的前 i 个字符转换为 word2 的前 j 个字符最少操作数
# dp[i - 1][j - 1] 表示 word1 的前 i - 1 个字符转换为 word2 的前 j -1 个字符最少操作数
# 比如 word1 为 abcd,word2 为 efgd
# 此时 i = 4,j = 4
# 由于 word1.charAt(4 - 1) = d
# word2.charAt(4 - 1) = d
# 所以如果知道了 abc 转换为 efg 的最少操作数,那么结果也就出来了
# d --> d 不需要执行任何操作
dp[i][j] = dp[i - 1][j - 1]
# 否则,说明 word1 中第 i - 1 个位置的字符(即当前遍历 i 时的字符)
# 与 word2 中第 j - 1 个位置的字符【不相等】(即当前遍历 j 时的字符)
else:
# 那么 dp[i][j] 可以从以下三种状态转换过来
# 1、word1(L1-1) ——> word2(L2-1)
# 2、word1(L1-1) ——> word2(L2)
# 3、word1(L1) ——> word2(L2-1)
# word1(L1-1) ——> word2(L2-1) 的最少操作数是 dp[i - 1][j - 1]
# word1(L1-1) ——> word2(L2) 的最少操作数是 dp[i - 1][j]
# word1(L1) ——> word2(L2-1) 的最少操作数是 dp[i][j - 1])
# 选这三种状态的较小值后,
# 1、如果是 word1(L1-1) ——> word2(L2-1) 过来的,再执行 1 次替换操作
# 2、如果是 word1(L1-1) ——> word2(L2) 过来的,再执行 1 次删除操作
# 3、如果是 word1(L1) ——> word2(L2-1) 过来的,再执行 1 次插入操作
dp[i][j] = min(dp[i - 1][j - 1],min(dp[i - 1][j],dp[i][j - 1])) + 1
# dp[L1][L2] 表示 word1 的前 L1 个字符转换为 word2 的前 L2 个字符最少操作数
# 把这个结果进行返回
return dp[L1][L2]